在第十九天,你已經掌握了 LiveData
的終極武器,學會如何讓 View
自動觀察 ViewModel
的資料變化。你現在已經具備 MVVM
的思維了!
今天,我們就要將這個的能力,應用在一個程式解題的任務上:羅馬數字轉換器。透過這個專案,你會親自體會到,使用專業架構如何讓複雜的程式邏輯,變得更清晰、更好維護。
當我們在處理像羅馬數字轉換這樣的複雜邏輯時,如果所有的程式碼都混在 MainActivity.java
裡,會讓程式變得非常難以維護。
而 MVVM
就能完美解決這個問題。我們會將:
MainActivity
(View)。ViewModel
(智慧助理) 裡面。LiveData
來包裝。這樣,MainActivity
就只剩下最基本的「顯示」和「傳遞使用者輸入」的工作,變得非常輕薄。
activity_main.xml
我們只需要一個 EditText
、一個 Button
和一個 TextView
。
`<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/colorInput"
android:layout_width="0dp"
android-layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:hint="請輸入羅馬數字"
android:minHeight="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/searchButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="轉換"
app:layout_constraintEnd_toEndOf="@+id/colorInput"
app:layout_constraintStart_toStartOf="@+id/colorInput"
app:layout_constraintTop_toBottomOf="@+id/colorInput" />
<TextView
android:id="@+id/resultTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="轉換結果將顯示在這裡"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="@+id/searchButton"
app:layout_constraintStart_toStartOf="@+id/searchButton"
app:layout_constraintTop_toBottomOf="@+id/searchButton" />
</androidx.constraintlayout.widget.ConstraintLayout>`
ViewModel
類別打開 MainActivity.java
所在的資料夾,右鍵點擊,選擇 New
-> Java Class
。給類別取名為 RomanConverterViewModel
。
LiveData
來持有轉換後的結果。`import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.HashMap;
public class RomanConverterViewModel extends ViewModel {
// 建立一個 MutableLiveData 來持有轉換結果
private final MutableLiveData<String> _result = new MutableLiveData<>();
public LiveData<String> result = _result;
// 羅馬數字與數值的對應表
private final HashMap<Character, Integer> romanMap;
public RomanConverterViewModel() {
// 在 ViewModel 創建時,初始化羅馬數字字典
romanMap = new HashMap<>();
romanMap.put('I', 1);
romanMap.put('V', 5);
romanMap.put('X', 10);
romanMap.put('L', 50);
romanMap.put('C', 100);
romanMap.put('D', 500);
romanMap.put('M', 1000);
_result.setValue(""); // 設定初始結果為空
}
// 這個方法是從 View 接收使用者輸入的入口
public void convertRomanToInteger(String romanString) {
if (romanString.isEmpty()) {
_result.setValue("請輸入羅馬數字!");
return;
}
try {
int convertedValue = performConversion(romanString.toUpperCase());
_result.setValue(String.valueOf(convertedValue)); // 成功後,更新 LiveData 的值
} catch (IllegalArgumentException e) {
_result.setValue("無效的羅馬數字!"); // 失敗時,更新 LiveData 的值
}
}
// 羅馬數字轉換的邏輯,與之前相同
private int performConversion(String s) {
int result = 0;
int previousValue = 0;
for (int i = s.length() - 1; i >= 0; i--) {
char currentChar = s.charAt(i);
Integer currentValue = romanMap.get(currentChar);
if (currentValue == null) {
throw new IllegalArgumentException("無效的羅馬數字字符");
}
if (currentValue < previousValue) {
result -= currentValue;
} else {
result += currentValue;
}
previousValue = currentValue;
}
return result;
}
}`
MainActivity.java
(View)打開你的檔案 MainActivity.java
。這次,Activity
只負責初始化,並設定 LiveData
的觀察者。
`import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.lifecycle.ViewModelProvider;
public class MainActivity extends AppCompatActivity {
private RomanConverterViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 取得 ViewModel 實例
viewModel = new ViewModelProvider(this).get(RomanConverterViewModel.class);
// 2. 找到 View 元件
EditText romanInput = findViewById(R.id.colorInput);
Button convertButton = findViewById(R.id.searchButton);
TextView resultTextView = findViewById(R.id.resultTextView);
// 3. 讓 View 開始「觀察」ViewModel 的結果
viewModel.result.observe(this, newResult -> {
// 當 ViewModel 的結果改變時,自動更新 TextView
resultTextView.setText(newResult);
});
// 4. 設定按鈕點擊事件
convertButton.setOnClickListener(v -> {
// 將使用者輸入傳遞給 ViewModel 進行處理
viewModel.convertRomanToInteger(romanInput.getText().toString());
});
}
}`
IX
、LXXX
等羅馬數字。今天我們成功地將 MVVM
架構應用在一個程式解題題目上,你學會了:
Activity
中分離,並放入 ViewModel
。LiveData
,實現資料變動後,View
自動更新畫面的效果。MVVM
如何讓你的 MainActivity
變得更輕薄、更專注於介面顯示。從明天開始,我們將進入一個更進階的內容:Fragment
,它是 MVVM
架構中不可或缺的一部分。
明天見!